#include "config.h"
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/un.h>

#include <signal.h>
#include <errno.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

#include <strings.h>

#include <pthread.h>

/* used in cliserv.h, so must come first */
#define MY_NAME "nbd_server"

#include "nbd_cmd.h"
#include "cliserv.h"
#include "exp_async_io.h"

/* Debugging macros */
#ifdef DODBG
#define DEBUG(...) printf(__VA_ARGS__)
#else
#define DEBUG(...)
#endif

/** global flags */
int glob_flags = 0;
int g_sock = 0;

/**
 * The highest value a variable of type off_t can reach. This is a signed
 * integer, so set all bits except for the leftmost one.
 **/
#define OFFT_MAX ~((off_t)1<<(sizeof(off_t)*8-1))

/** Global flags: */
#define F_OLDSTYLE 1      /**< Allow oldstyle (port-based) exports */
#define F_LIST 2      /**< Allow clients to list the exports on a server */
#define F_NO_ZEROES 4      /**< Do not send zeros to client */
#define F_DUAL_LISTEN 8      /**< Listen on both TCP and unix socket */
// also accepts F_FORCEDTLS (which is 16384)

/*
 * If we want what the system really has set we'd have to read
 * /proc/sys/fs/pipe-max-size, but for now 1mb should be enough.
 */
#define MAX_PIPE_SIZE (1 * 1024 * 1024)

#include "nbdsrv.h"

static int socket_read(CLIENT *client, void *buf, size_t len)
{
    if (readit(client->net, buf, len) < 0) {
        assert(client->socket_closed != NULL);
        client->socket_closed(client);
        return -1;
    }
    return 0;
}

/**
 * Consume data from a socket that we don't want
 *
 * @param c the client to read from
 * @param len the number of bytes to consume
 * @param buf a buffer
 * @param bufsiz the size of the buffer
 **/
static inline void consume(CLIENT *c, size_t len, void *buf, size_t bufsiz)
{
    size_t curlen;
    while (len > 0) {
        curlen = (len > bufsiz) ? bufsiz : len;
        socket_read(c, buf, curlen);
        len -= curlen;
    }
}

/**
 * Consume a length field and corresponding payload that we don't want
 *
 * @param c the client to read from
 **/
static inline void consume_len(CLIENT *c)
{
    uint32_t len;
    char buf[1024];

    socket_read(c, &len, sizeof(len));
    len = ntohl(len);
    consume(c, len, buf, sizeof(buf));
}

static void socket_write(CLIENT *client, void *buf, size_t len)
{
    if (writeit(client->net, buf, len) < 0) {
        assert(client->socket_closed != NULL);
        client->socket_closed(client);
    }
}

static inline void socket_closed_negotiate(CLIENT *client)
{
    err("Negotiation failed: %m");
}

static inline void socket_closed_transmission(CLIENT *client)
{
    int saved_errno = errno;
    errno = saved_errno;
//    err("Connection dropped: %m");
}


static void send_reply(CLIENT *client, uint32_t opt, uint32_t reply_type, ssize_t datasize, void *data)
{
    uint32_t len = (uint32_t) datasize;
    struct {
        uint64_t magic;
        uint32_t opt;
        uint32_t reply_type;
        uint32_t datasize;
    } __attribute__ ((packed)) header = {
            htonll(0x3e889045565a9LL),
            htonl(opt),
            htonl(reply_type),
            htonl(len),
    };
    if (datasize < 0) {
        len = (uint32_t) strlen((char *) data);
        header.datasize = htonl(len);
    }
    socket_write(client, &header, sizeof(header));
    if (data != NULL) {
        socket_write(client, data, len);
    }
}

uint8_t getmaskbyte(int masklen) {
    if(masklen >= 8) {
        return 0xFF;
    }
    uint8_t retval = 0;
    for(int i = 7; i + masklen > 7; i--) {
        retval |= 1 << i;
    }

    return retval;
}
/**
 * Find the name of the file we have to serve. This will use g_strdup_printf
 * to put the IP address of the client inside a filename containing
 * "%s" (in the form as specified by the "virtstyle" option). That name
 * is then written to client->exportname.
 *
 * @param net A socket connected to an nbd client
 * @param client information about the client. The IP address in human-readable
 * format will be written to a new char* buffer, the address of which will be
 * stored in client->clientname.
 * @return: 0 - OK, -1 - failed.
 **/
int set_peername(int net, CLIENT *client)
{
    struct sockaddr_storage netaddr;
    struct sockaddr *addr = (struct sockaddr *) &netaddr;
    socklen_t addrinlen = sizeof(struct sockaddr_storage);
    struct addrinfo hints;
    struct addrinfo *ai = NULL;
    char peername[NI_MAXHOST];
    char netname[NI_MAXHOST];
    char *tmp = NULL;
    int i;
    int e;

    if (getsockname(net, addr, &addrinlen) < 0) {
        msg(LOG_INFO, "getsockname failed: %m");
        return -1;
    }

    if (netaddr.ss_family == AF_UNIX) {
        client->clientaddr.ss_family = AF_UNIX;
        strcpy(peername, "unix");
    } else {
        if (getpeername(net, (struct sockaddr *) &(client->clientaddr), &addrinlen) < 0) {
            msg(LOG_INFO, "getpeername failed: %m");
            return -1;
        }
        if ((e = getnameinfo((struct sockaddr *) &(client->clientaddr), addrinlen,
                             peername, sizeof(peername), NULL, 0, NI_NUMERICHOST))) {
            msg(LOG_INFO, "getnameinfo failed: %s", gai_strerror(e));
            return -1;
        }

        memset(&hints, '\0', sizeof(hints));
        hints.ai_flags = AI_ADDRCONFIG;
        e = getaddrinfo(peername, NULL, &hints, &ai);

        if (e != 0) {
            msg(LOG_INFO, "getaddrinfo failed: %s", gai_strerror(e));
            freeaddrinfo(ai);
            return -1;
        }
    }

    if (strncmp(peername, "::ffff:", 7) == 0) {
        memmove(peername, peername + 7, strlen(peername));
    }

    if (client->server->virtstyle == VIRT_IPLIT) {
        msg(LOG_DEBUG, "virtstyle ipliteral");
        client->exportname = client->server->exportname;
    }

    freeaddrinfo(ai);
    client->clientname = strdup(peername);
    printf("connect from %s, assigned file is %s\n", client->clientname, client->exportname);
    return 0;
}

void send_export_info(CLIENT *client, SERVER *server, bool maybe_zeroes)
{
    uint64_t size_host = htonll((uint64_t) (client->exportsize));
    uint16_t flags = NBD_FLAG_HAS_FLAGS | NBD_FLAG_SEND_WRITE_ZEROES;

    socket_write(client, &size_host, 8);
    if (server->flags & F_READONLY)
        flags |= NBD_FLAG_READ_ONLY;
    if (server->flags & F_FLUSH)
        flags |= NBD_FLAG_SEND_FLUSH;
    if (server->flags & F_FUA)
        flags |= NBD_FLAG_SEND_FUA;
    if (server->flags & F_ROTATIONAL)
        flags |= NBD_FLAG_ROTATIONAL;
    if (server->flags & F_TRIM)
        flags |= NBD_FLAG_SEND_TRIM;
    if (!(server->flags & F_COPYONWRITE))
        flags |= NBD_FLAG_CAN_MULTI_CONN;
    flags = htons(flags);
    socket_write(client, &flags, sizeof(flags));
    if (!(glob_flags & F_NO_ZEROES) && maybe_zeroes) {
        char zeros[128];
        memset(zeros, '\0', sizeof(zeros));
        socket_write(client, zeros, 124);
    }
}

/**
  * Commit to exporting the chosen export
  *
  * When a client sends NBD_OPT_EXPORT_NAME or NBD_OPT_GO, we need to do
  * a number of things (verify whether the client is allowed access, try
  * to open files, etc etc) before we're ready to actually serve the
  * export.
  *
  * This function does all those things.
  *
  * @param client the CLIENT structure with .server and .net members set
  * up correctly
  * @return true if the client is allowed access to the export, false
  * otherwise
  */
static bool commit_client(CLIENT *client, SERVER *server)
{
    client->server = server;
    client->exportsize = OFFT_MAX;
    if (pthread_mutex_init(&(client->lock), NULL)) {
        msg(LOG_ERR, "Unable to initialize mutex");
        return false;
    }
    /* Check whether the client is listed in the authfile */
    if (set_peername(client->net, client)) {
        msg(LOG_ERR, "Failed to set peername");
        return false;
    }

    client->socket_closed = socket_closed_transmission;

    int rlt = client->exp_io.open(&client->exp_io, client->exportname);
    if (rlt) {
        return false;
    }
    client->exportsize = client->exp_io.get_device_size(&client->exp_io);
    msg(LOG_INFO, "Size of exported file/device is %llu", (unsigned long long) client->exportsize);

    setmysockopt(client->net);

    return true;
}

static CLIENT *handle_export_name(CLIENT *client, uint32_t opt, SERVER *servers, uint32_t cflags)
{
    (void) opt;
    uint32_t namelen;
    char *name;
    int i;

    socket_read(client, &namelen, sizeof(namelen));
    namelen = ntohl(namelen);
    if (namelen > 0) {
        name = malloc(namelen + 1);
        name[namelen] = 0;
        socket_read(client, name, namelen);
    } else {
        name = strdup("");
    }
//    for (i = 0; i < servers->len; i++)
    {
        SERVER *serve = servers;
        // hide exports that are TLS-only if we haven't negotiated TLS
        // yet
        if ((serve->flags & F_FORCEDTLS) && !client->tls_session) {
            assert(0);
        }
        if (!strcmp(serve->servename, name)) {
            client->clientfeats = cflags;
            free(name);
            if (!commit_client(client, serve)) {
                return NULL;
            }
            send_export_info(client, serve, true);
            return client;
        }
    }
    free(name);
    err("Negotiation failed/8a: Requested export not found, or is TLS-only and client did not negotiate TLS");
}

static void handle_list(CLIENT *client, uint32_t opt, SERVER *servers, uint32_t cflags)
{
    (void) cflags;
    uint32_t len;
    int i;
    char buf[1024];
    char *ptr = buf + sizeof(len);

    socket_read(client, &len, sizeof(len));
    len = ntohl(len);
    if (len) {
        send_reply(client, opt, NBD_REP_ERR_INVALID, -1,
                   "NBD_OPT_LIST with nonzero data length is not a valid request");
    }
    if (!(glob_flags & F_LIST)) {
        send_reply(client, opt, NBD_REP_ERR_POLICY, -1, "Listing of exports denied by server configuration");
        err_nonfatal("Client tried disallowed list option");
        return;
    }
//    for (i = 0; i < servers->len; i++)
    {
        SERVER *serve = servers;
        // Hide TLS-only exports if we haven't negotiated TLS yet
        if (!client->tls_session && (serve->flags & F_FORCEDTLS)) {
            assert(0);
        }
        len = htonl((uint32_t) strlen(serve->servename));
        memcpy(buf, &len, sizeof(len));
        strncpy(ptr, serve->servename, sizeof(buf) - sizeof(len));
        send_reply(client, opt, NBD_REP_SERVER, strlen(serve->servename) + sizeof(len), buf);
    }
    send_reply(client, opt, NBD_REP_ACK, 0, NULL);
}

/**
  * Handle an NBD_OPT_INFO or NBD_OPT_GO request.
  *
  * XXX this matches the proposal I sent out, rather than the officially
  * documented version of this command. Need to bring the two in sync
  * one way or the other.
  */
static bool handle_info(CLIENT *client, uint32_t opt, SERVER *servers, uint32_t cflags)
{
    uint32_t namelen, len;
    char *name;
    int i;
    SERVER *server = NULL;
    uint16_t n_requests;
    uint16_t request;
    char buf[1024];
    bool sent_export = false;
    uint32_t reptype = NBD_REP_ERR_UNKNOWN;
    char *msg = "Export unknown";

    socket_read(client, &len, sizeof(len));
    len = htonl(len);
    socket_read(client, &namelen, sizeof(namelen));
    namelen = htonl(namelen);
    if (namelen > (len - 6)) {
        send_reply(client, opt, NBD_REP_ERR_INVALID, -1,
                   "An OPT_INFO request cannot be smaller than the length of the name + 6");
        socket_read(client, buf, len - sizeof(namelen));
    }
    if (namelen > 0) {
        name = malloc(namelen + 1);
        name[namelen] = 0;
        socket_read(client, name, namelen);
    } else {
        name = strdup("");
    }
//    for (i = 0; i < servers->len; i++)
    {
        SERVER *serve = servers;
        if (!strcmp(serve->servename, name)) {
            if ((serve->flags & F_FORCEDTLS) && !client->tls_session) {
                reptype = NBD_REP_ERR_TLS_REQD;
                msg = "TLS is required for that export";
                assert(0);
            }
            server = serve;
        }
    }
    free(name);
    socket_read(client, &n_requests, sizeof(n_requests));
    n_requests = ntohs(n_requests);
    if (!server) {
        consume(client, n_requests * sizeof(request), buf,
                sizeof(buf));
        send_reply(client, opt, reptype, -1, msg);
        return false;
    }
    if (opt == NBD_OPT_GO) {
        client->clientfeats = cflags;
        if (!commit_client(client, server)) {
            send_reply(client, opt, NBD_REP_ERR_POLICY, -1, "Access denied by server configuration");
            return false;
        }
    }
    for (i = 0; i < n_requests; i++) {
        socket_read(client, &request, sizeof(request));
        switch (ntohs(request)) {
            case NBD_INFO_EXPORT:
                send_reply(client, opt, NBD_REP_INFO, 12, NULL);
                socket_write(client, &request, 2);
                send_export_info(client, server, false);
                sent_export = true;
                break;
            default:
                // ignore all other options for now.
                break;
        }
    }
    if (!sent_export) {
        request = htons(NBD_INFO_EXPORT);
        send_reply(client, opt, NBD_REP_INFO, 12, NULL);
        socket_write(client, &request, 2);
        send_export_info(client, server, false);
    }
    send_reply(client, opt, NBD_REP_ACK, 0, NULL);

    return true;
}

/**
 * Do the initial negotiation.
 *
 * @param net The socket we're doing the negotiation over.
 * @param servers The array of known servers.
 **/
CLIENT *negotiate(int net, SERVER *servers)
{
    uint16_t smallflags = NBD_FLAG_FIXED_NEWSTYLE | NBD_FLAG_NO_ZEROES;
    uint64_t magic;
    uint32_t cflags = 0;
    uint32_t opt;
    CLIENT *client = malloc(sizeof(CLIENT));
    client->exp_io = expand_standard;
    client->net = net;
    client->socket_closed = socket_closed_negotiate;

    assert(servers != NULL);
    /**
     * 1. Send 'NBDMAGIC' 64bit Magic
     */
    printf("%s: send magic=%s\n", __func__, INIT_PASSWD);
    socket_write(client, INIT_PASSWD, 8);
    /**
     * 2. Send 'IHAVEOPT' 64bit Magic
     */
    magic = htonll(opts_magic);
    printf("%s: send magic=0x%lx\n", __func__, opts_magic);
    socket_write(client, &magic, sizeof(magic));
    /**
     * 3. Send 16bit Handshake flags
     */
    smallflags = htons(smallflags);
    printf("%s: send flags=0x%x\n", __func__, smallflags);
    socket_write(client, &smallflags, sizeof(uint16_t));
    /**
     * 4. Read 32bit Client flags
     */
    socket_read(client, &cflags, sizeof(cflags));
    cflags = htonl(cflags);
    printf("%s: read client_flags=0x%x\n", __func__, cflags);
    if (cflags & NBD_FLAG_C_NO_ZEROES) {
        glob_flags |= F_NO_ZEROES;
    }
    do {
        /**
         * 5. Read 'IHAVEOPT' 64bit Magic
         */
        socket_read(client, &magic, sizeof(magic));
        magic = ntohll(magic);
        printf("%s: read magic=0x%lx\n", __func__, magic);
        if (magic != opts_magic) {
            err_nonfatal("Negotiation failed/5a: magic mismatch");
            goto handler_err;
        }
        /**
         * 5. Read 32bit Option
         */
        socket_read(client, &opt, sizeof(opt));
        printf("%s: read option=0x%x\n", __func__, opt);
        opt = ntohl(opt);
        if (client->tls_session == NULL
            && glob_flags & F_FORCEDTLS
            && opt != NBD_OPT_STARTTLS) {
            if (opt == NBD_OPT_EXPORT_NAME) {
                // can't send an error message for EXPORT_NAME,
                // so must do hard close
                goto handler_err;
            }
            if (opt == NBD_OPT_ABORT) {
                // handled below
                break;
            }
            consume_len(client);
            send_reply(client, opt, NBD_REP_ERR_TLS_REQD, -1, "TLS is required on this server");
            continue;
        }
        switch (opt) {
            case NBD_OPT_EXPORT_NAME:
                // NBD_OPT_EXPORT_NAME must be the last
                // selected option, so return from here
                // if that is chosen.
                printf("%s: option=%s\n", __func__, "NBD_OPT_EXPORT_NAME");
                if (handle_export_name(client, opt, servers, cflags) != NULL) {
                    return client;
                } else {
                    goto handler_err;
                }
                break;
            case NBD_OPT_LIST:
                handle_list(client, opt, servers, cflags);
                break;
            case NBD_OPT_ABORT:
                break;
            case NBD_OPT_STARTTLS:
                consume_len(client);
                send_reply(client, opt, NBD_REP_ERR_PLATFORM, -1, "This nbd-server was compiled without TLS support");
                break;
            case NBD_OPT_GO:
                printf("%s: option=%s\n", __func__, "NBD_OPT_GO");
            case NBD_OPT_INFO:
                printf("%s: option=%s\n", __func__, "NBD_OPT_INFO");
                if (handle_info(client, opt, servers, cflags) && opt == NBD_OPT_GO) {
                    return client;
                }
                break;
            default:
                consume_len(client);
                send_reply(client, opt, NBD_REP_ERR_UNSUP, -1,
                           "The given option is unknown to this server implementation");
                break;
        }
    } while ((opt != NBD_OPT_EXPORT_NAME) && (opt != NBD_OPT_ABORT));
    if (opt == NBD_OPT_ABORT) {
        err_nonfatal("Session terminated by client");
        goto handler_err;
    }
    err_nonfatal("Weird things happened: reached end of negotiation without success");
    handler_err:
    free(client);
    return NULL;
}

static int socket_accept(const int sock)
{
    struct sockaddr_storage addrin;
    socklen_t addrinlen = sizeof(addrin);
    int net = accept(sock, (struct sockaddr *) &addrin, &addrinlen);
    if (net < 0) {
        err_nonfatal("Failed to accept socket connection: %m");
    }
    return net;
}

static void handle_modern_connection(SERVER *const servers, const int sock)
{
    CLIENT *client = NULL;
    int sock_flags_old;
    int sock_flags_new;

    int net = socket_accept(sock);
    if (net < 0)
        return;

    sock_flags_old = fcntl(net, F_GETFL, 0);
    if (sock_flags_old == -1) {
        msg(LOG_ERR, "Failed to get socket flags");
        goto handler_err;
    }

    sock_flags_new = sock_flags_old & ~O_NONBLOCK;
    if (sock_flags_new != sock_flags_old &&
        fcntl(net, F_SETFL, sock_flags_new) == -1) {
        msg(LOG_ERR, "Failed to set socket to blocking mode");
        goto handler_err;
    }

    client = negotiate(net, servers);
    if (!client) {
        msg(LOG_ERR, "Modern initial negotiation failed");
        goto handler_err;
    }

    msg(LOG_INFO, "Starting to serve");
    mainloop_threaded(client);

    handler_err:
    close(net);
    free(client);
}

/**
 * Loop through the available servers, and serve them. Never returns.
 **/
void serveloop(SERVER *servers)
{
    int i;
    int mmax, max;
    fd_set mset;
    fd_set rset;
    sigset_t blocking_mask;
    sigset_t original_mask;

    /*
	 * Set up the master fd_set. The set of descriptors we need
	 * to select() for never changes anyway and it buys us a *lot*
	 * of time to only build this once. However, if we ever choose
	 * to not fork() for clients anymore, we may have to revisit
	 * this.
	 */
    mmax = 0;
    FD_ZERO(&mset);
    {
        int sock = g_sock;
        FD_SET(sock, &mset);
        mmax = sock > mmax ? sock : mmax;
    }

    /* Construct a signal mask which is used to make signal testing and
	 * receiving an atomic operation to ensure no signal is received between
	 * tests and blocking pselect(). */
    if (sigemptyset(&blocking_mask) == -1)
        err("failed to initialize blocking_mask: %m");

    if (sigaddset(&blocking_mask, SIGCHLD) == -1)
        err("failed to add SIGCHLD to blocking_mask: %m");

    if (sigaddset(&blocking_mask, SIGHUP) == -1)
        err("failed to add SIGHUP to blocking_mask: %m");

    if (sigaddset(&blocking_mask, SIGTERM) == -1)
        err("failed to add SIGTERM to blocking_mask: %m");

    if (sigprocmask(SIG_BLOCK, &blocking_mask, &original_mask) == -1)
        err("failed to block signals: %m");

    for (;;) {
        memcpy(&rset, &mset, sizeof(fd_set));
        max = mmax;

        if (pselect(max + 1, &rset, NULL, NULL, NULL, &original_mask) > 0) {
            DEBUG("accept, ");
            msg(LOG_INFO, "modernsocks->len=%d", modernsocks->len);
            if (!FD_ISSET(g_sock, &rset)) {
                continue;
            }

            handle_modern_connection(servers, g_sock);
            return;
        }
    }
}

/**
 * Set server socket options.
 *
 * @param socket a socket descriptor of the server
 *
 * @param gerror a pointer to an error object pointer used for reporting
 *        errors. On error, if gerror is not NULL, *gerror is set and -1
 *        is returned.
 *
 * @return 0 on success, -1 on error
 **/
int dosockopts(const int socket)
{
    int yes = 1;

    struct linger l;

    /* lose the pesky "Address already in use" error message */
    if (setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
        printf("failed to set socket option SO_REUSEADDR: %s", strerror(errno));
        return -1;
    }
    l.l_onoff = 1;
    l.l_linger = 10;
    if (setsockopt(socket, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) == -1) {
        printf( "failed to set socket option SO_LINGER: %s", strerror(errno));
        return -1;
    }
    if (setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(int)) == -1) {
        printf("failed to set socket option SO_KEEPALIVE: %s", strerror(errno));
        return -1;
    }

    return 0;
}

int open_unix(const char *const sockname)
{
    struct sockaddr_un sa;
    int retval = -1;

    memset(&sa, 0, sizeof(struct sockaddr_un));
    sa.sun_family = AF_UNIX;
    strncpy(sa.sun_path, sockname, sizeof sa.sun_path);
    sa.sun_path[sizeof(sa.sun_path) - 1] = '\0';
    int sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock < 0) {
        printf( "failed to open a unix socket: failed to create socket: %s", strerror(errno));
        goto out;
    }
    if (bind(sock, (struct sockaddr *) &sa, sizeof(struct sockaddr_un)) < 0) {
        printf( "failed to open a unix socket: failed to bind to address %s: %s", sockname, strerror(errno));
        goto out;
    }
    if (listen(sock, 10) < 0) {
        printf( "failed to open a unix socket: failed to start listening: %s", strerror(errno));
        goto out;
    }
    retval = 0;
    g_sock = sock;
    out:
    if (retval < 0 && sock >= 0) {
        close(sock);
    }

    return retval;
}

int open_modern(const char *const addr, const char *const port)
{
    struct addrinfo hints;
    struct addrinfo *ai = NULL;
    struct addrinfo *ai_bak = NULL;
    int e;
    int retval = -1;
    int sock = -1;
    char **addrs;
    char *ip_addr = "0.0.0.0";
    char const *l_addr = addr;

    if (!addr || strlen(addr) == 0) {
        l_addr = "::, 0.0.0.0";
    }

//    printf("%s: addr=%s, port=%s", __func__, addr, port);
//    addrs = g_strsplit_set(l_addr, ", \t", -1);

//    for (int i = 0; addrs[i] != NULL; i++) {
//        if (addrs[i][0] == '\0') {
//            continue;
//        }
    memset(&hints, '\0', sizeof(hints));
    hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_family = AF_UNSPEC;
    hints.ai_protocol = IPPROTO_TCP;
    e = getaddrinfo(ip_addr, port ? port : NBD_DEFAULT_PORT, &hints, &ai);
    ai_bak = ai;
    if (e != 0  && g_sock == 0) {
        printf( "failed to open a modern socket: failed to get address info: %s", gai_strerror(e));
        goto out;
    }

    while (ai != NULL) {
        sock = -1;

        if ((sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0) {
            printf( "failed to open a modern socket: failed to create a socket: %s", strerror(errno));
            goto out;
        }

        if (dosockopts(sock) == -1) {
            printf("failed to open a modern socket: \n");
            goto out;
        }

        if (bind(sock, ai->ai_addr, ai->ai_addrlen)) {
            /*
             * Some systems will return multiple entries for the
             * same address when we ask it for something
             * AF_UNSPEC, even though the first entry will
             * listen to both protocols. Other systems will
             * return multiple entries too, but we actually
             * do need to open both.
             *
             * Handle this by ignoring EADDRINUSE if we've
             * already got at least one socket open
             */
            if (errno == EADDRINUSE && g_sock > 0) {
                goto next;
            }
            printf( "failed to open a modern socket: failed to bind an address to a socket: %s", strerror(errno));
            goto out;
        }

        if (listen(sock, 10) < 0) {
            printf( "failed to open a modern socket: failed to start listening on a socket: %s", strerror(errno));
            goto out;
        }

        next:
        ai = ai->ai_next;
    }
    g_sock = sock;
    if (ai_bak) {
        freeaddrinfo(ai_bak);
        ai_bak = NULL;
    }
//    }

    retval = 0;
    out:

    if (retval == -1 && sock >= 0) {
        close(sock);
    }
    if (ai_bak)
        freeaddrinfo(ai_bak);

    return retval;
}

/**
 * Connect our servers.
 **/
void setup_servers(const char *const modernaddr, const char *const modernport, const char *unixsock, const int flags)
{
    struct sigaction sa;

    if (unixsock != NULL) {
        if (open_unix(unixsock) == -1) {
            msg(LOG_ERR, "failed to setup servers: %s", gerror->message);
            exit(EXIT_FAILURE);
        }
    }
    if (((flags & F_DUAL_LISTEN) != 0) || (unixsock == NULL)) {
        if (open_modern(modernaddr, modernport) == -1) {
            msg(LOG_ERR, "failed to setup servers: %s", gerror->message);
            exit(EXIT_FAILURE);
        }
    }
}

int nbd_server(SERVER *serve)
{
    setup_servers(NULL, serve->net_port, NULL, 0);

    serveloop(serve);

    printf("nbd-server: %s exit\n", __func__);
    return EXIT_SUCCESS;
}
